/*
 * Copyright (c) 2006-2021, Ulandlink Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-11-17     xpw          first version
 * 2023-02-20     xpw          sync
 */
 
#include <dfs.h>
#include <dfs_posix.h>

#define LOG_TAG              "http.upld"
#define LOG_LVL               LOG_LVL_INFO
#include <at_log.h>

#include <http_pub.h>

#if HTTP_SERVER_ENABLE

#include <at_device.h>
#include <http_upload.h>
#include "app_system_msg.h"
#include "app_tool_api.h"
#include "web_tool_api.h"
#include "app_lcd_mp.h"

extern void http_send_cgi_resp(int socket, char *buffer, int length);

#define WEB_FILE_PATH_MAXLEN    128
#define WEB_READ_FILE_MAXLEN    3096

static int file_size = 0;
static int upload_event;
static char file_path[WEB_FILE_PATH_MAXLEN + 1] = {0};

rt_bool_t reconnect_enable = RT_TRUE;

static void lcd_msg_post(rt_bool_t enabled)
{
    switch (upload_event) {
        case WEB_UPLOAD_SYS_FW:
            app_lcd_fail_msg_post(APP_LCD_FAIL_MAINTAIN, enabled);
            break;
        
        default:
            break;
    }
}

static void net_device_control(rt_bool_t enabled, http_request_t *request)
{
    struct at_device *device = RT_NULL;
    
    device = at_device_get_by_name(AT_DEVICE_NAMETYPE_NETDEV, "esp32");
    if (!device) {
        return;
    }
        
    switch (upload_event) {
        case WEB_UPLOAD_SYS_FW:
            if  (rt_strstr(request->host, "192.168.1.1")) {
                if (enabled) {
                    reconnect_enable = RT_FALSE;
                    at_device_control(device, AT_DEVICE_CTRL_WIFI_DISCONNECT, RT_NULL);
                } else {
                    reconnect_enable = RT_TRUE;
                    at_device_control(device, AT_DEVICE_CTRL_WIFI_CONNECT, RT_NULL);
                }
            }
            break;
        
        default:
            break;
    }
}

static void _create_file_path(int upload_event, const char *filename)
{
    char *file_dir;
    
    rt_memset(file_path, 0, sizeof(file_path));
    
    switch (upload_event) {
        case WEB_UPLOAD_SYS_FW:
            file_dir = WEB_SYS_FW_PKG_DIR;
            rt_snprintf(file_path, WEB_FILE_PATH_MAXLEN, "%s%s", file_dir, filename);
            break;
        
        case WEB_UPLOAD_OCPP_CER:
            rt_snprintf(file_path, WEB_FILE_PATH_MAXLEN, "%s", OCPP_MAIN_CA_PATH);
            break; 
        
        case WEB_UPLOAD_WEB_FW:
        case WEB_UPLOAD_15118_FW:
        case WEB_UPLOAD_15118_CER:
        default:
            file_dir = "/";
            rt_snprintf(file_path, WEB_FILE_PATH_MAXLEN, "/%s", filename);
            break;
    }
    
    if (access(file_dir, 0) < 0) {
        mkdir(file_dir, 0);
    }
    
    LOG_D("file will save to %s", file_path);
}

static int upload_write(int fd, char *buffer, int length)
{
    if (fd < 0 || !buffer) {
        return 0;
    }
    
    rt_kprintf("write: length %d\n", length);
    
    return write(fd, buffer, length);
}    

static void discard_file(int socket)
{
    char *buffer;
    char temp[16];
    int ret, length = 0;
    
    buffer = (char *)rt_malloc(WEB_READ_FILE_MAXLEN);
    if (buffer != RT_NULL) {
        while (1) {
            if (file_size - length <= WEB_READ_FILE_MAXLEN) {
                ret = at_recv(socket, buffer, file_size - length, 0);
            } else {
                ret = at_recv(socket, buffer, WEB_READ_FILE_MAXLEN, 0);
            }
            
            if (ret > 0) {
                rt_kprintf("discard: length %d\n", ret);
                length += ret;
            }
            
            if (length >= file_size) {
                rt_free(buffer);
                return;
            }
        }
    } else {
        while (1) {
            if (file_size - length <= 16) {
                ret = at_recv(socket, temp, file_size - length, 0);
            } else {
                ret = at_recv(socket, temp, 16, 0);
            }
            
            if (ret > 0) {
                rt_kprintf("discard: length %d\n", ret);
                length += ret;
            }
            
            if (length >= file_size) {
                return;
            }
        }
    }
}

static rt_base_t download_file(int socket, rt_bool_t if_save)
{
#define _DATA_RECV_TIMEOUT_    RT_TICK_PER_SECOND * 5
    
    int timeout;
    char *buffer;
    char temp[8];
    rt_tick_t start;
    int fd, ret, length = 0;
    
    timeout = file_size;
    fd = open(file_path, O_WRONLY | O_CREAT, 0);
    buffer = (char *)rt_calloc(1, WEB_READ_FILE_MAXLEN);
    
    start = rt_tick_get();
    while(1) {
        /* check timeout */
        if (rt_tick_get() - start > _DATA_RECV_TIMEOUT_) {
            LOG_E("recv data timeout(%d), %d remains.", _DATA_RECV_TIMEOUT_, file_size - length);
            if (buffer) {
                rt_free(buffer);
            }
            if (fd >= 0) {
                close(fd);
            }
            return -RT_ERROR;
        }
        
        if (buffer) {
            if (file_size - length <= WEB_READ_FILE_MAXLEN) {
                ret = at_recv(socket, buffer, file_size - length, MSG_DONTWAIT);
            } else {
                ret = at_recv(socket, buffer, WEB_READ_FILE_MAXLEN, MSG_DONTWAIT);
            }
        } else {
            if (file_size - length <= sizeof(temp)) {
                ret = at_recv(socket, temp, file_size - length, MSG_DONTWAIT);
            } else {
                ret = at_recv(socket, temp, sizeof(temp), MSG_DONTWAIT);
            }
        }
        
        if (ret > 0) {
            upload_write(fd, buffer, ret);
            length += ret;
            start = rt_tick_get();
        }
        
        if (length == file_size) {
            LOG_I("receive %d bytes success.", file_size);
            rt_free(buffer);
            if (fd >= 0) {
                close(fd);
            }
            return length;
        }        
    }
}

static int upload_done(int socket, int result_code, char *error_msg)
{
    const char *msg;
    rt_bool_t reboot = RT_FALSE;
    rt_bool_t is_success = RT_TRUE;
    
    static const char *updt_sys_fail = "Update system failed!";
    static const char *updt_sys_success = "Upload success, system restarted";
    static const char *upld_ocpp_cer = "Update OCPP certificate successfully";
    static const char *updt_web_success = "Update web firmware successfully";
    static const char *updt_web_fail = "Update web firmware failed!";
    static const char *sys_err = "System error!";
    
    if (result_code != 200) {
        msg = error_msg;
        is_success = RT_FALSE;
    } else {
        switch (upload_event) {
            case WEB_UPLOAD_SYS_FW:
                if (copy_to_dl_part(file_path, file_size) < 0) {
                    msg = updt_sys_fail;
                    is_success = RT_FALSE;
                } else {
                    msg = updt_sys_success;
                    reboot = RT_TRUE;
                }
            break;
            
            case WEB_UPLOAD_OCPP_CER:
                msg = upld_ocpp_cer;
            break;
            
            case WEB_UPLOAD_WEB_FW:
                if (web_tool_decompress_file(file_path)) {
                    msg = updt_web_success;
                } else {
                    msg = updt_web_fail;
                    is_success = RT_FALSE;
                }
                remove(file_path);
            break;
                
            case WEB_UPLOAD_15118_FW:
            case WEB_UPLOAD_15118_CER:
            default:
                msg = sys_err;
                is_success = RT_FALSE;
            break;
        }
    }
    
    http_send_cgi_resp(socket, (char *)msg, strlen(msg));    
    
    if (is_success) {
        LOG_I("%s", msg);
    } else {
        LOG_E("%s", msg);
    }
    
    if (reboot) {
        app_system_reset_msg_post();
    }
    
    return 0;
}

void http_resp_upload_request(int socket, http_request_t *request)
{
    static const char *_err = "Upload failed, please try it later";
    
    int ret;
    char *ptr, *err;
    int read_length;
    char name0[32] = {0};
    char name1[32] = {0};
    int result_code = 500;
    char buffer[256] = {0};
    char option[32] = {0};
    char split_str[64] = {0};
    char end_str0[] = "\r\n";
    char end_str1[] = "\r\n\r\n";
    char end_str2[] = "--\r\n";
    rt_bool_t support_flag = RT_FALSE;
    char filename[WEB_FILE_PATH_MAXLEN] = {0};
    rt_uint64_t length = request->content_length;
    
    err = (char *)_err;
    
    ptr = rt_strstr(request->content_type, "boundary");
    if (ptr == RT_NULL) {
        LOG_E("content_type: missing boundary.");
        goto __exit;
    }
    
    ptr += 9;
    rt_memcpy(split_str, ptr, sizeof(split_str));
    LOG_D("get split string(%s).", split_str);
    
    /* skip first split string */
    read_length = strlen(split_str) + 4;
    ret = at_recv(socket, buffer, read_length, MSG_DONTWAIT);
    if (ret != read_length) {
        LOG_E("read first split failed.");
        goto __exit;
    }
    length -= ret;
    
    /* read first file header */
    rt_memset(buffer, 0, sizeof(buffer));
    ret = http_recv_str_end(socket, buffer, 0, end_str1, strlen(end_str1), RT_TICK_PER_SECOND);
    if (ret < 0) {
        goto __exit;
    }
    sscanf(buffer, "%*[^:]: %*[^;]; name=\"%[^\"]\"", name0);
    LOG_D("name0 is [%s]", name0);
    length -= ret;
    
    /* read first file content */
    ret = http_recv_str_end(socket, option, sizeof(option), end_str0, strlen(end_str0), RT_TICK_PER_SECOND);
    if (ret < 0) {
        goto __exit;
    }
    *(option + ret - 2) = '\0';
    LOG_D("option is [%s]", option);
    length -= ret;
    
    /* skip \r\n and second split string */
    read_length = strlen(split_str) + 6;
    ret = at_recv(socket, buffer, read_length, MSG_DONTWAIT);
    if (ret != read_length) {
        LOG_E("skip CR/LF and second split string failed.");
        goto __exit;
    }
    length -= ret;
    
    /* read second file header */
    rt_memset(buffer, 0, sizeof(buffer));
    ret = http_recv_str_end(socket, buffer, 0, end_str1, strlen(end_str1), RT_TICK_PER_SECOND);
    if (ret < 0) {
        goto __exit;
    }
    sscanf(buffer, "%*[^:]: %*[^;]; name=\"%[^\"]\"; filename=\"%[^\"]\"", name1, filename);
    LOG_D("name1 is [%s], filename is [%s]", name1, filename);
    length -= ret;
    
    /* get size of file */
    file_size = length - strlen(split_str) - 8;
    
    /* read file content*/
    upload_event = atoi(option + 3) - 99;
    
    lcd_msg_post(RT_TRUE);
    net_device_control(RT_TRUE, request);
    
    for (int i = 0; i < sizeof(support_events); i ++) {
        if (upload_event == support_events[i]) {
            support_flag = RT_TRUE;
            break;
        }
    }

    if (support_flag) {
        /* check file name and size */
        ptr = web_tool_check_upload_filename(filename, file_size, upload_event);
        if (ptr != RT_NULL) {
            err = ptr;
            discard_file(socket);
            goto __exit;
        }
        
        LOG_I("web is %s.", web_upload_event_str[upload_event - 1]);
        
        _create_file_path(upload_event, filename);
        if (access(file_path, 0) == 0) {
            if (remove(file_path) < 0) {
                LOG_E("file(%s) can not be removed.");
            }
        }
        
        if (download_file(socket, RT_TRUE) < 0) {
            goto __exit;
        }
    } else {
        LOG_E("upload event(%d) not support.", upload_event);
        discard_file(socket);
        goto __exit;
    }
    
    /* read remains of http request */
    read_length = strlen(split_str) + 8;
    ret = at_recv(socket, buffer, read_length, MSG_DONTWAIT);
    if (ret != read_length) {
        LOG_E("read remains of http request failed.");
        goto __exit;
    }
    
    /* check the ending of http request */
    if (!rt_strncmp(buffer + read_length - strlen(end_str2), end_str2, strlen(end_str2))) {
        result_code = 200;
    }
    
__exit:
    
    upload_done(socket, result_code, (char *)err);
    
    lcd_msg_post(RT_FALSE);
    net_device_control(RT_FALSE, request);
    
    if (result_code != 200) {
        LOG_E("recv file failed.");
    }
}

#endif
